﻿using System;
using System.Collections.Generic;
using System.Text;

namespace RedisInAction
{
    /// <summary>
    /// Redis布隆过滤器
    /// </summary>
    public class BloomFilter
    {
        public string Key = "";
        public UInt64 BloomBitLength { get; }
        public int DataCount { get; }
        public int HashCount { get; }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="BloomBitLength">布隆数组的大小</param>
        /// <param name="DataCount">预估数据长度</param>
        /// <param name="HashCount">hash数</param>
        public BloomFilter(string Key, uint BloomBitLength, int DataCount, int HashCount)
        {
            this.BloomBitLength = BloomBitLength;
            this.DataCount = DataCount;
            this.HashCount = HashCount;
            this.Key = Key;
        }

        /// <summary>
        /// 添加值
        /// </summary>
        /// <param name="key"></param>
        public void Add(string key)
        {
            for (int i = 0; i < HashCount; i++)
            {
                UInt64 index = 0;
                if (i == 0)
                {
                    var hashCode = Y.Common.MD5.Md5Hash(key);
                    index = hashCode % BloomBitLength;
                }
                else
                {
                    var hashCode = Y.Common.MD5.Md5Hash(RepeatString(key, i + 1));
                    index = hashCode % BloomBitLength;
                }
                RedisService.RedisFactory.DefaultRedis.StringSetBit(Key,Convert.ToInt64(index) , true);
            }
        }

        /// <summary>
        /// 检测键值是否存在
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Exist(string key) 
        {
            for (int i = 0; i < HashCount; i++)
            {
                UInt64 index = 0;
                if (i == 0)
                {
                    var hashCode = Y.Common.MD5.Md5Hash(key);
                    index = hashCode % BloomBitLength;
                }
                else
                {
                    var hashCode = Y.Common.MD5.Md5Hash(RepeatString(key, i + 1));
                    index = hashCode % BloomBitLength;
                }
                if (!RedisService.RedisFactory.DefaultRedis.StringGetBit(Key, Convert.ToInt64(index)))
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 错误概率
        /// </summary>
        /// <returns></returns>
        public double getFalsePositiveProbability()
        {
            // (1 - e^(-k * n / m)) ^ k
            return Math.Pow((1 - Math.Exp(-HashCount * (double)DataCount / BloomBitLength)),
                    HashCount);
        }

        private string RepeatString(string str, int n)
        {
            char[] arr = str.ToCharArray();
            char[] arrDest = new char[arr.Length * n];
            for (int i = 0; i < n; i++)
            {
                Buffer.BlockCopy(arr, 0, arrDest, i * arr.Length * 2, arr.Length * 2);
            }
            return new string(arrDest);

        }

    }
}
